/*
 * Copyright (C), 2002-2017, 你的兔宝宝电子商务有限公司
 * FileName: BaseDalClient.java
 * Author:   你的兔宝宝
 * Date:     2017-6-5 下午8:41:14
 * Description: 数据库操作客户端默认实现//模块目的、功能描述      
 * History: //修改记录
 * <author>      <time>      <version>    <desc>
 * 修改人姓名             修改时间            版本号                  描述
 */
package com.baby.demo.utils.dal;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;

import com.baby.demo.utils.Collections;
import com.baby.demo.utils.Maps;
import com.baby.demo.utils.Strings;
import com.baby.demo.utils.dal.dialects.BaseSqlDialect;
import com.baby.demo.utils.dal.dialects.SqlDialect;

/**
 * 数据库操作客户端默认实现<br> 
 * 〈功能详细描述〉
 *
 * @author 你的兔宝宝
 * @see [相关类/方法]（可选）
 * @since [产品/模块版本] （可选）
 */
public class BaseDalClient extends SqlMapXmlResource implements DalClient,InitializingBean {
    
    private static Logger logger = LoggerFactory.getLogger(BaseDalClient.class);
    
    /** 数据源 */
    private DataSource dataSource;
    
    /**
     * 数据库类型，默认mysql
     */
    String dbtype = "mysql";
    
    Map<String,SqlDialect> dialects;
    
    SqlDialect dialect;
    
    int pagesize = 10;
    
    @Override
    public void afterPropertiesSet() throws Exception {
        // 初始化xml
        initialize();
        initDialect();
    }
    
    protected void initDialect() {
        if( dialects == null ){
            dialects = new HashMap<String,SqlDialect>();
            dialects.put("mysql", new BaseSqlDialect());
        }
        if( dialect == null ){
            dialect = dialects.get(dbtype);
        }
    }
    
    @Override
    public Map<String, Object> queryForMap(String sqlId, Map<String, Object> params) {
        // get jdbc template
        List<Map<String, Object>> list = queryForList(sqlId, params);
        return Maps.one(list);
    }

  
    @Override
    public List<Map<String, Object>> queryForList(String sqlId, Map<String, Object> params) {
        List<Map<String, Object>> list = new DalPrepare<List<Map<String, Object>>>() {
            @Override
            public List<Map<String, Object>> prepare(NamedParameterJdbcTemplate jdbcTemplate, String sql, Map<String, Object> params) {
                return jdbcTemplate.queryForList(sql, params);
            }
        }.exec(sqlId, params);
        return list;
    }
    
    @Override
    public Map<String, Object> queryForPage(String sqlId, Map<String, Object> params,final int page,final int pagesize) {
        Map<String,Object> pageobj = new DalPrepare<Map<String,Object>>(){
            @Override
            public Map<String, Object> prepare(NamedParameterJdbcTemplate jdbcTemplate, String sql, Map<String, Object> params) {
                Map<String,Object> pagination = Maps.noop();
                //count
                String coutSql = dialect.makeCountSql(sql);
                //page
                String pageSql = dialect.makePageSql(page, pagesize, sql);
                int total = jdbcTemplate.queryForInt(coutSql, params);
                List<Map<String, Object>> list = jdbcTemplate.queryForList(pageSql, params);
                pagination.put("total", total);
                pagination.put("list", list);
                pagination.put("totalpage", calculateTotalpage(pagesize,total));
                pagination.put("page", page);
                pagination.put("pagesize", pagesize);
                return pagination;
            }
        }.exec(sqlId, params);
        return pageobj;
    }
    
    @Override
    public Map<String, Object> queryForPage(String sqlId, Map<String, Object> params) {
        final BaseDalClient self = this;
        Map<String,Object> pageobj = new DalPrepare<Map<String,Object>>(){
            @Override
            public Map<String, Object> prepare(NamedParameterJdbcTemplate jdbcTemplate, String sql, Map<String, Object> params) {
                Map<String,Object> pagination = Maps.noop();
                int page = Strings.getInt(params, "page", 0);
                int pagesize = Strings.getInt(params, "pagesize", self.pagesize);
                //count
                String coutSql = dialect.makeCountSql(sql);
                //page
                String pageSql = dialect.makePageSql(page, pagesize, sql);
                int total = jdbcTemplate.queryForInt(coutSql, params);
                List<Map<String, Object>> list = jdbcTemplate.queryForList(pageSql, params);
                pagination.put("total", total);
                pagination.put("list", list);
                pagination.put("totalpage", calculateTotalpage(pagesize,total));
                pagination.put("page", page);
                pagination.put("pagesize", pagesize);
                return pagination;
            }
        }.exec(sqlId, params);
        return pageobj;
    }
  
    @Override
    public int execute(String sqlId, Map<String, Object> params) {
        return executeForPK(sqlId,params,null).intValue();
    }
   
    @Override
    public Number executeForPK(String sqlId, Map<String, Object> params) {
        return executeForPK(sqlId,params,new GeneratedKeyHolder());
    }
    
    @Override
    public Number executeForPK(String sqlId, Map<String, Object> params,final KeyHolder keyHolder) {
        Number num = new DalPrepare<Number>() {
            @Override
            public Number prepare(NamedParameterJdbcTemplate jdbcTemplate, String sql, Map<String, Object> params) {
                Number num = null;
                if( keyHolder != null ){
                    // get jdbc template
                    jdbcTemplate.update(sql, new MapSqlParameterSource(params), keyHolder);
                    num = keyHolder.getKey();
                }else{
                    num = jdbcTemplate.update(sql, new MapSqlParameterSource(params));
                }
                return num;
            }
        }.exec(sqlId, params);
        return num;
    }

   
    @Override
    public int[] batchUpdate(String sqlId, final Map<String, Object>[] batchValues) {
        Map<String, Object> params = Maps.one(batchValues);
        int r[] = new DalPrepare<int[]>() {
            @Override
            public int[] prepare(NamedParameterJdbcTemplate jdbcTemplate, String sql, Map<String, Object> params) {
                return jdbcTemplate.batchUpdate(sql, batchValues);
            }
        }.exec(sqlId, params);
        return r;
    }
    
    /**
     * 
     * 数据库操作准备<br> 
     * 〈功能详细描述〉
     *
     * @author 你的兔宝宝
     * @see [相关类/方法]（可选）
     * @since [产品/模块版本] （可选）
     */
    public abstract class DalPrepare<T>{
        String name = "DalPrepare";
        public DalPrepare() {}
        public DalPrepare(String name) {
            this.name = name;
        }
        public T exec(String sqlId, Map<String, Object> params){
            params = Collections.show(params);
            T result = null;
            // 表路由
            routeTable(params);
            // get sql
            SqlMap sqlMap = getSqlMap(sqlId);
            // freemarker parse sql
            String sql = SqlFreeMarkerParser.parse(sqlId, sqlMap.getContent(), params);
            logger.debug("sqlId[{}],sql[{}],params[{}]",new Object[]{sqlId,sql,params});
            NamedParameterJdbcTemplate jdbcTemplate = getJdbcTemplate(sqlId,sqlMap);
            //计时
            long begin = System.currentTimeMillis();
            result = prepare(jdbcTemplate,sql,params);
            long cost = System.currentTimeMillis() - begin;
            //日志
            logger.debug("{}-sqlId[{}],cost[{}]",new Object[]{name,sqlId,cost});
            return result;
        }
        public abstract T prepare(NamedParameterJdbcTemplate jdbcTemplate,String sql,Map<String, Object> params);
    }
    
    /**
     * 
     * 功能描述: 计算总页数<br>
     * 〈功能详细描述〉
     *
     * @param pagesize
     * @param total
     * @return
     * @Author:   你的兔宝宝
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    protected int calculateTotalpage(int pagesize,int total) {
        int page = 0;
        if( pagesize >0 ){
            page = total / pagesize;
            page += (total % pagesize)>0?1:0;
        }
        return page;
    }
    
    /**
     * 
     * 功能描述: 表路由暂不实现<br>
     * 〈功能详细描述〉
     *
     * @param params
     * @Author:   你的兔宝宝
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    protected void routeTable(Map<String, Object> params){
        // NOOP
    }
    protected NamedParameterJdbcTemplate getJdbcTemplate(Object params, SqlMap sqlMap) {
        JdbcTemplate classicJdbcTemplate = new JdbcTemplate(dataSource);
        classicJdbcTemplate.setQueryTimeout(sqlMap.getJdbcTimeout());
        return new NamedParameterJdbcTemplate(classicJdbcTemplate);
    }
    /**
     * @param dataSource the dataSource to set
     */
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * @return the dataSource
     */
    public DataSource getDataSource() {
        return dataSource;
    }

    /**
     * @return the dbtype
     */
    public String getDbtype() {
        return dbtype;
    }

    /**
     * @param dbtype the dbtype to set
     */
    public void setDbtype(String dbtype) {
        this.dbtype = dbtype;
    }

    /**
     * @return the dialects
     */
    public Map<String, SqlDialect> getDialects() {
        return dialects;
    }

    /**
     * @param dialects the dialects to set
     */
    public void setDialects(Map<String, SqlDialect> dialects) {
        this.dialects = dialects;
    }

    /**
     * @return the dialect
     */
    public SqlDialect getDialect() {
        return dialect;
    }

    /**
     * @param dialect the dialect to set
     */
    public void setDialect(SqlDialect dialect) {
        this.dialect = dialect;
    }

    /**
     * @return the pagesize
     */
    public int getPagesize() {
        return pagesize;
    }

    /**
     * @param pagesize the pagesize to set
     */
    public void setPagesize(int pagesize) {
        this.pagesize = pagesize;
    }
    
}
